home *** CD-ROM | disk | FTP | other *** search
Text File | 1997-02-25 | 18.9 KB | 416 lines | [TEXT/PJMM] |
- { NeoTextBox: Faster, script-aware alternative to TETextBox }
- {}
- { Originally written in C by Bryan K. Ressler, from Develop issue 9 }
- { Pascal conversion by Peter N. Lewis }
-
-
- { Modifications by Sebastiano Pilla }
- {}
- { 1) Added optional saving/restoring of the clip region. This is controlled by the inSwapClipping parameter: if set to }
- { TRUE, then NeoTextBox saves the current clip region, clips its drawing to inWrapBox, then restores the previously }
- { saved clip region at the end of the drawing; if set to FALSE, then NeoTextBox uses whatever clip region is current }
- {}
- { 2) Added a check to avoid drawing text outside of inWrapBox, because it will be clipped anyway. However, the entire text }
- { must still be traversed to determine the correct line breaks }
- {}
- { 3) Added a special one-line case, to avoid the wrapping loop where it is known that the text fits all into the given box }
-
- unit NeoTextBox;
-
-
- interface
-
-
- uses
- Types;
-
-
- const
- ntbJustFull = 128;
-
-
- { NeoTextBox }
- {}
- { Word-wraps text inside a given box }
- {}
- { Entry: inTextPtr = pointer to the text we need to wrap }
- { inTextLen = length (in bytes) of the text }
- { inWrapBox = the box within which we're wrapping }
- { inAlign = the text alignment }
- { teForceLeft, teFlushLeft -> left justified }
- { teJustCenter, teCenter -> center justified }
- { teJustRight, teFlushRight -> right justified }
- { ntbJustFull -> full justified }
- { teJustLeft, teFlushDefault -> system justified }
- { inLhCode = line height code }
- { < 0 -> variable - based on tallest character }
- { 0 -> default - based on GetFontInfo }
- { > 0 -> fixed - use inLhCode as the line height }
- { inSwapClipping = TRUE if NeoTextBox should save and restore the clipping, FALSE if this is unnecessary }
- { Exit: outEndY = vertical coordinate of the last line, if non-zero }
- { outLhUsed = line height used to draw the text, if non-zero }
- { function result = total number of lines drawn (even outside of the given box) }
- function NeoTextBox (inTextPtr: Ptr;
- inTextLen: UInt32;
- inWrapBox: Rect;
- inAlign: SInt16;
- inLhCode: SInt16;
- var outEndY: SInt16;
- var outLhUsed: SInt16;
- inSwapClipping: Boolean): SInt16;
-
-
- implementation
-
-
- uses
- Fonts, FixMath, QuickDrawText, Script, TextUtils;
-
-
- const
- kReturnChar = $D;
-
-
- { Max }
- {}
- { Returns the maximum between the two given numbers }
- {}
- { Entry: inNum1 = first number }
- { inNum2 = second number }
- { Exit: function result = maximum between inNum1 and inNum2 }
- function Max (inNum1, inNum2: SInt32): SInt32;
- inline
- $201F, $2E9F, $B097, $6F02, $2E80;
-
-
- { AddPtrLong }
- {}
- { Adds an offset to the given pointer }
- {}
- { Entry: inPtr = pointer (first operand) }
- { inOffset = offset (second operand) to be added to inPtr }
- { Exit: function result = pointer to Ord4(inPtr) + inOffset }
- function AddPtrLong (inPtr: univ Ptr;
- inOffset: SInt32): Ptr;
- inline
- $201F, { move.l (sp)+,d0 ; pop inOffset }
- $D09F, { add.l (sp)+,d0 ; add inPtr to inOffset (and pop inPtr) }
- $2E80; { move.l d0,(sp) ; place in result }
-
-
- { NTBLineHeight }
- {}
- { Figures line height }
- {}
- { Entry: inTextPtr = entire text given to NeoTextBox }
- { inTextLen = length (in bytes) of the text }
- { inWrapBox = box within we're wrapping }
- { inLhCode = line height code given to NeoTextBox }
- { Exit: outStartY = starting vertical pen location }
- { function result = line height to use }
- function NTBLineHeight (inTextPtr: Ptr;
- inTextLen: UInt32;
- inWrapBox: Rect;
- inLhCode: SInt16;
- var outStartY: SInt16): UInt16;
- var
- fInfo: FontInfo; { old-style font information record }
- frac: Point; { fraction for the TrueType calls }
- asc, desc: SInt16; { used in the OutlineMetrics calls }
- lineHeight: UInt16; { return value }
- err: OSErr;
- begin
-
- GetFontInfo(fInfo);
- if inLhCode < 0 then
- begin
-
- { -------------------------------------------------------------------------------------------------- }
- { If the user has specified variable-height lines, we need to try to determine the tallest ascent in the given text. }
- { We can only really do this if the font is a TrueType font. Otherwise, we punt and use }
- { old - fashioned GetFontInfo numbers . }
- { -------------------------------------------------------------------------------------------------- }
-
- frac.h := 1;
- frac.v := 1;
- if IsOutline(frac, frac) then
- begin
-
- { ---------------------------------------------------------------------------------------------- }
- { At this Point we know the current font is a TrueType font, so we do an OutlineMetrics call with our full text. }
- { It will put the tallest character ascent into asc, and the deepest descent into desc. Then we choose between }
- { whichever's most between the old-style ascent/descent and the numbers we get from the OutlineMetrics call. }
- { ---------------------------------------------------------------------------------------------- }
-
- err := OutlineMetrics(UInt16(inTextLen), inTextPtr, frac, frac, asc, desc, nil, nil, nil);
- lineHeight := Max(fInfo.ascent, asc) + Max(fInfo.descent, -desc) + fInfo.leading;
- outStartY := inWrapBox.top + Max(fInfo.ascent, asc) + fInfo.leading;
- end
-
- else
- begin
-
- { ------------------------------------------------------------------------------------------------- }
- { At this Point we know the current font isn't TrueType, so we just use the old way of calculating line height. }
- { ------------------------------------------------------------------------------------------------- }
-
- lineHeight := Max(fInfo.ascent, asc) + Max(fInfo.descent, -desc) + fInfo.leading;
- outStartY := inWrapBox.top + Max(fInfo.ascent, asc) + fInfo.leading;
- end;
- end
-
- else if inLhCode = 0 then
- begin
-
- { ---------------------------------------------------------------------------------------------------- }
- { If the user has specified "default" line height, he just wants us to get the line height from the FontInfo record. }
- { ---------------------------------------------------------------------------------------------------- }
-
- lineHeight := fInfo.ascent + fInfo.descent + fInfo.leading;
- outStartY := inWrapBox.top + fInfo.ascent + fInfo.leading;
-
- end
- else
- begin
-
- { ----------------------------------------------------------------------------------------------------- }
- { If the user has provided a specific line height, we just trust them. We can't really generate too good of a }
- { starting vertical coordinate, but we munge one together anyway. }
- { ----------------------------------------------------------------------------------------------------- }
-
- lineHeight := inLhCode;
- outStartY := inWrapBox.top + inLhCode + fInfo.leading;
-
- end;
-
- NTBLineHeight := lineHeight;
- end;
-
-
- { NTBDraw }
- {}
- { Draws a line with appropriate justification }
- {}
- { Entry: inLineStartPtr = pointer to the beginning of the text for the current line }
- { inLineBytes = length (in bytes) of the text for this line }
- { inWrapBox = box within which we're wrapping }
- { inAlign = text alignment as specified by the user }
- { inCurY = our current vertical pen coordinate }
- { inBoxWidth = width of inWrapBox (since NeoTextBox already calculated it) }
- { inBreakCode = break code returned from StyledLineBreak }
- procedure NTBDraw (inLineStartPtr: Ptr;
- inLineBytes: SInt32;
- inWrapBox: Rect;
- inAlign, inCurY, inBoxWidth: SInt16;
- inBreakCode: StyledLineBreakCode);
- var
- blackLen: UInt32; { length of non-white characters }
- slop: SInt16; { number of pixels of slop for full just }
- begin
-
- { ------------------------------------------------------------------------------------------------------------ }
- { The first thing we do here is determine the length of the "black" part of the current line. This excludes spaces, carriage }
- { returns, and other white stuff depending on the language. How do we know what to eliminate? We DON'T! So we ask our }
- { friend the Script Manager to do it for us. VisibleLength returns the number of bytes that we should use for pixel width }
- { calculations. }
- { ------------------------------------------------------------------------------------------------------------ }
-
- blackLen := VisibleLength(inLineStartPtr, inLineBytes);
-
- if inAlign = ntbJustFull then
- begin
-
- { --------------------------------------------------------------------------------------------------------- }
- { For full justification, we need to calculate the "slop" space on the line that's not filled up by the text. Then we }
- { move to the margin and get ready to draw BUT WAIT! If this is the last line of a paragraph, we need to draw it with }
- { whatever the system justification is. So if the break code indicates we're at the end of the text, or we can find a }
- { carriage return there ourselves, we just change the text alignment to the current default system text alignment and }
- { fall through without drawing. If it's just another line within a paragraph, we use the handy Script Manager routine }
- { DrawJust to draw it justified. In languages like Arabic, full justification is performed differently than just spacing }
- { out the words. DrawJust handles cases like these correctly. }
- { Note that when we go looking for the carriage return at the end of the line, it's okay to simply index to the last Byte }
- { of the string. This is because the carriage return character, 0x0d, is in the control-code range, which is defined to }
- { never be the low Byte of a two Byte character. }
- { --------------------------------------------------------------------------------------------------------- }
-
- slop := inBoxWidth - TextWidth(inLineStartPtr, 0, blackLen);
- MoveTo(inWrapBox.left, inCurY);
- if (inBreakCode = smBreakOverflow) | (AddPtrLong(inLineStartPtr, inLineBytes - 1)^ = kReturnChar) then
- inAlign := GetSysDirection
- else
- DrawJust(inLineStartPtr, blackLen, slop);
- end;
-
- { ------------------------------------------------------------------------------------------------------------ }
- { For the rest of the text alignments (left, center, and right), we just move the pen to the right place and draw the text }
- { with DrawText. Note that the alignments that could have come into the NeoTextBox call have been resolved down to one }
- { of the four constants teForceLeft, teJustRight, teJustCenter, or ntbJustFull. }
- { ------------------------------------------------------------------------------------------------------------ }
-
- case inAlign of
- teForceLeft, teJustLeft:
- MoveTo(inWrapBox.left, inCurY);
- teJustRight:
- MoveTo(inWrapBox.right - TextWidth(inLineStartPtr, 0, blackLen), inCurY);
- teJustCenter:
- MoveTo(inWrapBox.left + (inBoxWidth - TextWidth(inLineStartPtr, 0, blackLen)) div 2, inCurY);
- otherwise
- ;
- end;
-
- if inAlign <> ntbJustFull then
- DrawText(inLineStartPtr, 0, inLineBytes);
- end;
-
-
- { NeoTextBox }
- {}
- { Word-wraps text inside a given box }
- {}
- { Entry: inTextPtr = pointer to the text we need to wrap }
- { inTextLen = length (in bytes) of the text }
- { inWrapBox = the box within which we're wrapping }
- { inAlign = the text alignment }
- { inLhCode = line height code }
- { inSwapClipping = TRUE if NeoTextBox should save and restore the clipping }
- { Exit: outEndY = vertical coordinate of the last line, if non-zero }
- { outLhUsed = line height used to draw the text, if non-zero }
- { function result = total number of lines drawn (even outside of the given box) }
- function NeoTextBox (inTextPtr: Ptr;
- inTextLen: UInt32;
- inWrapBox: Rect;
- inAlign: SInt16;
- inLhCode: SInt16;
- var outEndY: SInt16;
- var outLhUsed: SInt16;
- inSwapClipping: Boolean): SInt16;
- var
- oldClip: RgnHandle; { saved clipping region }
- lineStartPtr: Ptr; { pointer to beginning of a line }
- textEndPtr: Ptr; { pointer to the end of input text }
- fixedMax: Fixed; { boxWidth converted to fixed point }
- wrapWid: Fixed; { width to wrap to }
- lineBytes: SInt32; { number of bytes in one line }
- textLeft: UInt32; { pointer to remaining bytes of text }
- boxWidth: SInt16; { width of the wrapBox }
- lineHeight: UInt16; { calculated line height }
- curY: SInt16; { current vertical pen location }
- lineCount: UInt16; { number of lines we've drawn }
- breakCode: StyledLineBreakCode; { returned code from StyledLineBreak }
- begin
-
- { ---------------------------------------------------------------------------------------------------------- }
- { Check the parameters we're given, and exit immediately if they're wrong. }
- { ---------------------------------------------------------------------------------------------------------- }
- if (inTextPtr = nil) or (inTextLen = 0) then
- Exit(NeoTextBox);
-
- { ---------------------------------------------------------------------------------------------------------- }
- { First, we save the old clipping region and clip to wrapBox, if requested. Then, figure the width of wrapBox, and make }
- { a fixed point version of it. Also, resolve the text alignment teFlushDefault to be the default system text alignment. }
- { ---------------------------------------------------------------------------------------------------------- }
-
- if inSwapClipping then
- begin
- oldClip := NewRgn;
- GetClip(oldClip);
- ClipRect(inWrapBox);
- end;
- boxWidth := inWrapBox.right - inWrapBox.left;
- if inAlign = teFlushDefault then
- inAlign := GetSysDirection;
-
- { ---------------------------------------------------------------------------------------------------------- }
- { Now we call NTBLineHeight to calculate the appropriate line height. It also figures our starting vertical pen location in }
- { curY based on the line height and other metric parameters. Clear lineCount, set lineStartPtr to point to the beginning of }
- { the text, calculate textEnd for comparison to know when we're done, and preset textLeft to be all the text. }
- { ---------------------------------------------------------------------------------------------------------- }
-
- lineHeight := NTBLineHeight(inTextPtr, inTextLen, inWrapBox, inLhCode, curY);
- lineCount := 0;
- lineStartPtr := inTextPtr;
- textEndPtr := AddPtrLong(inTextPtr, inTextLen);
- textLeft := inTextLen;
-
- { ------------------------------------------------------------------------------------------------------------ }
- { Special one-line case: if the width of the input text is less than boxWidth, we avoid the wrapping loop and call NTBDraw }
- { directly to draw the single line with the appropriate justification. }
- { Note that the TextWidth call uses a 16-bit value to specify the number of bytes, where the inTextLen parameter given }
- { to NeoTextBox is a 32-bit value. So, we hope that the width of inWrapBox is small enough to let us correctly handle this }
- { special case. }
- { ------------------------------------------------------------------------------------------------------------ }
- if boxWidth > TextWidth(inTextPtr, 0, LoWrd(inTextLen)) then
- begin
- NTBDraw(inTextPtr, inTextLen, inWrapBox, inAlign, curY, boxWidth, smBreakOverflow);
- textLeft := 0; { avoids entering the wrapping loop }
- end;
-
- { ------------------------------------------------------------------------------------------------------------ }
- { This is the main wrap-and-draw loop. I bet you never thought wrapping text could be so easy... }
- { ------------------------------------------------------------------------------------------------------------ }
-
- fixedMax := Long2Fix(boxWidth);
- while textLeft > 0 do
- begin
-
- { ------------------------------------------------------------------------------------------------------- }
- { Every line, we have to preset lineBytes to something non-zero. This tells StyledLineBreak that we're drawing the }
- { first format run on the line (of course, for us, there's only ONE format run total). Also preset wrapWid. }
- { StyledLineBreak will always modify lineBytes (to tell you how many bytes are on this line), and will modify }
- { wrapWid, so we have to reset them each line. }
- { ------------------------------------------------------------------------------------------------------- }
-
- lineBytes := 1;
- wrapWid := fixedMax;
-
- breakCode := StyledLineBreak(lineStartPtr, textLeft, 0, textLeft, 0, wrapWid, lineBytes);
-
- { ------------------------------------------------------------------------------------------------------ }
- { Now that the Script Manager has done all the really hard work for us, we draw the line. We already knew lineStart, }
- { StyledLineBreak gave us lineBytes, which we pass to NTBDraw. It'll handle the different text alignments itself. }
- { Note that we really shouldn't draw text outside inWrapBox, but nonetheless it is necessary to break the entire text }
- { into lines, to have the correct line breaks: this means that we call NTBDraw only if curY <= inWrapBox.bottom + lineHeight }
- { ------------------------------------------------------------------------------------------------------ }
-
- if curY <= inWrapBox.bottom + lineHeight then
- NTBDraw(lineStartPtr, lineBytes, inWrapBox, inAlign, curY, boxWidth, breakCode);
-
- { ------------------------------------------------------------------------------------------------------ }
- { Now we advance our vertical position down by the height of one line, advance lineStartPtr by the number of bytes }
- { we just drew, calculate a new textLeft, and increment our line count. }
- { ------------------------------------------------------------------------------------------------------ }
-
- curY := curY + lineHeight;
- lineStartPtr := AddPtrLong(lineStartPtr, lineBytes);
- textLeft := textLeft - lineBytes;
- lineCount := lineCount + 1;
-
- end;
-
- { --------------------------------------------------------------------------------------------------------- }
- { Well that was a job well done. Let's return some useful values, too. Stuff our ending vertical coordinate and the line }
- { height we calculated into outEndY and outLHUsed, respectively. These allow the guy to put something after the }
- { wrapped text (or at least know the right place to put it). }
- { --------------------------------------------------------------------------------------------------------- }
-
- outEndY := curY - lineHeight;
- outLhUsed := lineHeight;
-
- { --------------------------------------------------------------------------------------------------------- }
- { Finally, restore the clipping region (if requested), dispose of the region, and return the TOTAL number of lines drawn }
- { (note that we didn't stop drawing when curY advanced past inWrapBox->bottom. This way, the user could tell that the text }
- { overflowed inWrapBox. If you want to know how many lines fit, divide inWrapBox by outLhUsed. This way, you get the }
- { best of both worlds. }
- { --------------------------------------------------------------------------------------------------------- }
-
- if inSwapClipping then
- begin
- SetClip(oldClip);
- DisposeRgn(oldClip);
- end;
-
- NeoTextBox := lineCount;
- end;
-
-
- end.